package matchbox;

import java.util.HashMap;

import utils.Pair;
import basics.FeaturesSet;
import basics.VectorMatrix;
import basics.VectorMatrixUtils;
import basics.VectorMatrixUtils.OperationOpt;

public abstract class Classifier {

	public static String trainEval(Classifier classifier, FeaturesSet X, FeaturesSet Y, FeaturesSet R,
			FeaturesSet X_eval, FeaturesSet Y_eval, FeaturesSet R_eval, final int k) {

		double mean_r = 0d;
		for (int i = 0; i < X.size(); i++) {
			mean_r += VectorMatrixUtils.mean(R.get(i), OperationOpt.NonZero);
		}
		mean_r /= (double) X.size();

		HashMap<Integer, Double> mean_ir = new HashMap<Integer, Double>();
		for (int j = 0; j < Y.size(); j++) {
			double s = 0;
			double n = 0;
			for (int i = 0; i < X.size(); i++) {
				if (R.get(i).get(j) > 0) {
					s += R.get(i).get(j);
					n++;
				}
			}
			if (n > 0) {
				mean_ir.put(j, s / n);
			} else {
				mean_ir.put(j, 0D);
			}
		}

		classifier.train(X, Y, R, k);

		int correct = 0;
		Double mae = 0.;
		double mae_u_mean = 0;
		double mae_i_mean = 0;
		Double rmse = 0.;
		double rmse_u_mean = 0;
		double rmse_i_mean = 0;
		int n = 0;
		for (int i = 0; i < X_eval.size(); i++) {
			VectorMatrix u = X_eval.get(i);
			Pair<Integer, Pair<Double, Double>> mae_rmse = classifier.testUser(u, i, Y_eval, R_eval, 75. / 100.);
			n += mae_rmse.getKey();
			mae += mae_rmse.getValue().getKey();
			rmse += mae_rmse.getValue().getValue();
		}

		String str = String.format("Accuracy: %2.0f, MAE  = %f, Mean-u-MAE  = %f, Mean-i-MAE  = %f\n", (double) correct
				* 100 / (double) n, mae / (double) n, mae_u_mean / (double) n, mae_i_mean / (double) n);
		str = str
				+ String.format("              RMSE = %f, Mean-u-RMSE = %f, Mean-i-RMSE = %f", rmse / (double) n,
						rmse_u_mean / (double) n, rmse_i_mean / (double) n);
		return str;
	}

	protected Pair<Integer, Pair<Double, Double>> testUser(VectorMatrix u, int userIdx, FeaturesSet y_eval,
			FeaturesSet r_eval, double percent) {
		Pair<Integer, Pair<Double, Double>> p = new Pair<Integer, Pair<Double, Double>>();
		p.setKey(0);
		p.setValue(new Pair<Double, Double>(0., 0.));

		int rated = 0;

		for (int j = 0; j < y_eval.size(); j++) {
			if (r_eval.get(userIdx).get(j) > 0) {
				rated++;
			}
		}

		if (rated < 1)
			return p;

		int test = 0;
		for (int j = 0, n = 0; j < y_eval.size(); j++) {
			if (r_eval.get(userIdx).get(j) > 0) {
				if (n < percent * rated) {
					trainUserItem(u, y_eval.get(j), r_eval.get(userIdx).get(j));
				} else {
					double res = infer(u, y_eval.get(j));
					double err = Math.abs(res - r_eval.get(userIdx).get(j));
					test++;
					p.setKey(test);
					p.getValue().setKey(p.getValue().getKey() + err);
					p.getValue().setValue(p.getValue().getValue() + (err * err));
				}
				n++;
			}
		}

		return p;
	}

	// protected abstract Pair<Integer, Pair<Double, Double>> testUser(VectorMatrix u, int i, FeaturesSet y_eval,
	// FeaturesSet r_eval, double d);

	protected abstract void trainUserItem(VectorMatrix u, VectorMatrix vectorMatrix, double r);

	public abstract double infer(VectorMatrix u, VectorMatrix it);

	public abstract void train(FeaturesSet users, FeaturesSet items, FeaturesSet ratings, int k);

}
